"""Game's location representations"""

import itertools
import location_debug
import game_piece as gamepiece_module

class Location(location_debug.LocationDebug):

    @staticmethod
    def transfer_game_piece(game_piece, loc_from, loc_to):
        '''Move a game_piece in loc_from into loc_to. '''

        # print 'Location from: %s' % repr(loc_from)
        # print 'Location to: %s' % repr(loc_to)
        # print 'From contains game piece: ', loc_from.contains_game_piece(game_piece)
        # print 'To contains game piece: ', loc_to.contains_game_piece(game_piece)
        # print 'from occupants: ', loc_from.occupants
        # print 'to occupants: ', loc_to.occupants
        
        if (not loc_from.contains_game_piece(game_piece)) or loc_to.contains_game_piece(game_piece):
            raise Exception( 'Cannot move %s from %s to %s' % (repr(game_piece), repr(loc_from), repr(loc_to)))
        
        loc_from._leave(game_piece)
        loc_to._enter(game_piece)

    def __init__(self, position):
        self.occupants = set()
        self.position = position

    # Private function - not meant to be used 
    def _enter(self, game_piece):
        '''
        add a game_piece from location.
        Private function - not meant to be used directly. please use transfer_game_piece instead
        '''        
        if game_piece in self.occupants:
            raise Exception('Unable to add game_piece %s to  - already entered' % (gamepiece_module.name, repr(self)))
        
        self.occupants.add( game_piece )


    def _leave(self, game_piece):
        '''
        remove a game_piece from location.
        Private function - not meant to be used. please use transfer_game_piece instead        
        '''
        if game_piece not in self.occupants:
            raise Exception( 'Unable to remove game_piece %s from room %s - not entered' % (gamepiece_module.name, repr(self)))

        self.occupants.remove( game_piece )

    def _query_occupants(self, pred = lambda x: True):
        return itertools.ifilter(pred, self.occupants)

    def contains_game_piece(self, game_piece):
        return game_piece in self.occupants

    def can_be_reached(self, loc_from):
        return False

    def is_full(self):
        return False

class StartingSquare(Location, location_debug.StartingSquareDebug):
    def __init__(self, game_piece, position):
        if type(game_piece) is not gamepiece_module.PlayerPiece:
            raise Exception('Only player pieces can exist in StartingSquare.  Invalid piece: %s' % repr(game_piece))

        super(StartingSquare, self).__init__(position)
        self.hallway = None
        super(StartingSquare, self)._enter(game_piece)
        game_piece.move_piece( self )

    def _enter(self, game_piece):
        raise Exception( 'Cannot re-enter starting square' )

    def is_full(self):
        """Don't allow players to move into this square"""
        return True  

    def __repr__(self):
        return 'Starting square attached to %s' % repr(self.hallway)

class Room(Location, location_debug.RoomDebug):
    valid_directions = set(['N', 'S', 'E', 'W', 'SecretPassage'])

    @staticmethod
    def transfer_weapon(weapon, from_room, to_room):
        from_room._removeWeapon(weapon)
        to_room._add_weapon(weapon)
    
    def __init__(self, name, position):
        super(Room, self).__init__(position)
        self.entrances = {'N': None,
                          'S': None,
                          'E': None,
                          'W': None }
        self.name = name

    def _enter(self, game_piece):
        super(Room, self)._enter(game_piece)

    def _leave(self, game_piece):
        super(Room, self)._leave(game_piece)

    def connect_entrance(self, direction, entrance):
        if direction not in Room.valid_directions:
            raise Exception( 'Invalid hallway direction: %s' % str(direction))

        self.entrances[ direction ] = entrance
        
    def can_be_reached(self, loc_from):
        '''
        Returns True if Room can be reached from loc_from.
        '''
        return loc_from in self.entrances.itervalues()

    def __repr__(self):
        return 'The %s' % self.name

class Hallway(Location, location_debug.HallwayDebug):
    valid_orientations = set(['EW', 'NS'])
    valid_directions = { 'EW' : set(['E','W']),
                         'NS' : set(['N','S']) }

    def __init__(self, orientation, (direction1, room1), (direction2, room2), position, starting_square = None):

        if orientation not in Hallway.valid_orientations:
            raise Exception( 'Invalid Orientation: %s' % repr(orientation) )
        
        super(Hallway, self).__init__(position)
        self.rooms = {'EW' : {'E': None,
                              'W': None },
                      'NS' : {'N': None,
                              'S': None }}
        self.starting_square = starting_square
        self.orientation = orientation
        self.addRoom(direction1, room1)
        self.addRoom(direction2, room2)


    def dirValid(self, direction):
        return direction in Hallway.valid_directions[ self.orientation ]

    def isConnected(self, direction):
        return self.rooms[ self.orientation ][ direction ] is not None
            
    def addRoom(self, direction, room): 
        if not self.dirValid(direction) or self.isConnected(direction):
            raise Exception( 'Invalid direction, or room already exists at direction: %s - %s' % (repr(direction), repr(room)))

        self.rooms[ self.orientation ][ direction ] = room


    def is_full(self):
        '''Is hallway fully occupied?  NOTE: Hard-coding maximum hallway entrants to 1'''
        return self._occupant_count() >= 1

    def can_be_reached(self, loc_from):
        '''
        Returns True if Room can be reached from loc_from.
        '''
        rooms = (v for dct in self.rooms.itervalues() for v in dct.itervalues())
        return loc_from in rooms or loc_from is self.starting_square

    def _enter(self, game_piece):
        if type(game_piece) is not gamepiece_module.PlayerPiece:
            raise Exception( 'Only a player piece can enter a Hallway.  Invalid piece: %s' % repr(game_piece))
        
        if self.is_full():
            raise Exception( 'Maximum occupants exceeded for %s - current occupants: %s' % (self.name, repr(self)))

        super(Hallway,self)._enter(game_piece)

    def _leave(self,game_piece):
        super(Hallway, self)._leave(game_piece)

    def _occupant_count(self):
        occupants = tuple(self._query_occupants(lambda x: type(x) is gamepiece_module.PlayerPiece))
        return len(occupants)

    def _get_rooms(self):
        rooms = self.rooms[ self.orientation ]
        valid_directions = Hallway.valid_directions[ self.orientation ]
        return (rooms[r] for r in valid_directions)        

    def __repr__(self):
        (r1,r2) = self._get_rooms()
        return 'The hallway between %s and %s' % (repr(r1), (repr(r2)))


def move_game_piece(game_piece, loc_to, contiguous = False):
    '''
    Interface function used for moving a game piece.  Ensures that game state is updated correctly.
    '''

#    print 'Moving game_piece %s from: %s to %s' % (repr(game_piece), repr(game_piece.get_location()), repr(loc_to))

    loc_from = game_piece.get_location()
    game_piece.move_piece(loc_to, contiguous)
    Location.transfer_game_piece(game_piece, loc_from, loc_to)


if __name__ == '__main__':
    pass
